package com.unisound.tracelog.server.ui.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.unisound.tracelog.common.model.dto.ResultDTO;
import com.unisound.tracelog.common.model.pojo.PageResult;
import com.unisound.tracelog.server.mapper.TraceLogMapper;
import com.unisound.tracelog.server.model.entity.TraceLog;
import com.unisound.tracelog.server.service.TraceLogService;
import com.unisound.tracelog.server.ui.converter.TraceLogListPageRequestToTraceLogConverter;
import com.unisound.tracelog.server.ui.converter.TraceLogResponseToExportJsonResponseConverter;
import com.unisound.tracelog.server.ui.converter.TraceLogToResponseConverter;
import com.unisound.tracelog.server.ui.model.request.MainTraceLogSearchRequest;
import com.unisound.tracelog.server.ui.model.request.TraceLogListPageRequest;
import com.unisound.tracelog.server.ui.model.request.TraceLogSearchChildrenRequest;
import com.unisound.tracelog.server.ui.model.request.TraceLogSearchRequest;
import com.unisound.tracelog.server.ui.model.response.ExportJsonResponse;
import com.unisound.tracelog.server.ui.model.response.TraceLogResponse;
import com.unisound.tracelog.server.ui.service.UITraceLogService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class UITraceLogServiceImpl implements UITraceLogService {
    private final TraceLogMapper traceLogMapper;
    private final TraceLogService traceLogService;
    private final TraceLogToResponseConverter traceLogToResponseConverter;
    private final TraceLogResponseToExportJsonResponseConverter traceLogResponseToExportJsonResponseConverter;
    private final TraceLogListPageRequestToTraceLogConverter traceLogListPageRequestToTraceLogConverter;

    @Override
    public ResultDTO<PageResult<TraceLogResponse>> searchMain(MainTraceLogSearchRequest request) {
        Page<TraceLog> page = new Page<>(request.getPageNum(), request.getPageSize(), true);

        LambdaQueryWrapper<TraceLog> wrapper = new LambdaQueryWrapper<>(TraceLog.builder()
                .traceId(request.getTraceId())
                .parentSpanId(0L)
                .serviceName(request.getServerName())
                .name(request.getName())
                .resultFlag(request.getResultFlag())
                .build());
        wrapper.orderByDesc(TraceLog::getTraceId);

        IPage<TraceLog> pageResult = traceLogMapper.selectPage(page, wrapper);

        PageResult<TraceLogResponse> result = new PageResult(
                pageResult.getTotal(), pageResult.getPages(), pageResult.getCurrent(), pageResult.getSize(),
                pageResult.getRecords().stream().map(a -> traceLogToResponseConverter.convert(a)).collect(Collectors.toList()));
        return ResultDTO.success(result);
    }

    @Override
    public ResultDTO<TraceLogResponse> search(TraceLogSearchRequest request) {
        if (request.getTraceId() == null) return ResultDTO.success();

        TraceLog traceLog = TraceLog.builder().traceId(request.getTraceId()).build();
        return ResultDTO.success(listTraceLogResponse(traceLog));
    }

    @Override
    public ResultDTO<TraceLogResponse> searchChildren(TraceLogSearchChildrenRequest request) {
        if (request.getTraceId() == null) return ResultDTO.success();
        if (request.getSpanId() == null) return ResultDTO.success();

        TraceLog traceLog = TraceLog.builder()
                .traceId(request.getTraceId())
                .parentSpanId(request.getSpanId())
                .build();
        return ResultDTO.success(listTraceLogResponse(traceLog));
    }

    private List<TraceLogResponse> listTraceLogResponse(TraceLog traceLog) {
        LambdaQueryWrapper<TraceLog> wrapper = new LambdaQueryWrapper<>(traceLog);
        wrapper.orderByAsc(TraceLog::getSpanId);
        List<TraceLog> traceLogs = traceLogService.list(wrapper);

        List<TraceLogResponse> responses = traceLogs.stream()
                .map(a -> traceLogToResponseConverter.convert(a))
                .collect(Collectors.toList());
        return responses;
    }

    @Override
    public ResultDTO<ExportJsonResponse> exportJson(TraceLogSearchRequest request) {
        ResultDTO<TraceLogResponse> searchDto = search(request);
        if (!ResultDTO.success(searchDto) || CollectionUtils.isEmpty(searchDto.getDataList()))
            return ResultDTO.success();

        List<TraceLogResponse> dataList = searchDto.getDataList();
        return ResultDTO.success(treeExportJsonResponse(dataList));
    }

    private ExportJsonResponse treeExportJsonResponse(List<TraceLogResponse> dataList) {
        List<TraceLogResponse> rootTraceLogResponses = getTraceLogResponses(0L, dataList);
        if (CollectionUtils.isEmpty(rootTraceLogResponses) || rootTraceLogResponses.size() != 1) return null;

        ExportJsonResponse exportJsonResponse = traceLogResponseToExportJsonResponseConverter.convert(rootTraceLogResponses.get(0));
        fillExportJsonResponse(exportJsonResponse, dataList);

        if (CollectionUtils.isNotEmpty(dataList)) {
            for (TraceLogResponse traceLogResponse : dataList) {
                ExportJsonResponse otherExportJsonResponse = traceLogResponseToExportJsonResponseConverter.convert(traceLogResponse);
                exportJsonResponse.getChildren().add(otherExportJsonResponse);
            }
        }
        return exportJsonResponse;
    }

    private void fillExportJsonResponse(ExportJsonResponse exportJsonResponse, List<TraceLogResponse> dataList) {
        if (CollectionUtils.isEmpty(dataList)) return;

        List<TraceLogResponse> currParentSpanIdTraceLogResponses = getTraceLogResponses(exportJsonResponse.getSpanId(), dataList);
        if (CollectionUtils.isEmpty(currParentSpanIdTraceLogResponses)) return;


        exportJsonResponse.setChildren(currParentSpanIdTraceLogResponses.stream()
                .map(traceLogResponse -> {
                    ExportJsonResponse jsonResponse = traceLogResponseToExportJsonResponseConverter.convert(traceLogResponse);
                    fillExportJsonResponse(jsonResponse, dataList);
                    return jsonResponse;
                })
                .collect(Collectors.toList()));
    }

    private List<TraceLogResponse> getTraceLogResponses(Long parentSpanId, List<TraceLogResponse> dataList) {
        List<TraceLogResponse> currParentSpanIdTraceLogResponses = new ArrayList<>();
        Iterator<TraceLogResponse> iterator = dataList.iterator();
        while (iterator.hasNext()) {
            TraceLogResponse traceLogResponse = iterator.next();
            if (traceLogResponse.getParentSpanId().equals(parentSpanId)) {
                currParentSpanIdTraceLogResponses.add(traceLogResponse);
                iterator.remove();
            }
        }
        return currParentSpanIdTraceLogResponses;
    }

    @Override
    public ResultDTO<PageResult<TraceLogResponse>> listPage(TraceLogListPageRequest request) {
        Page<TraceLog> page = new Page<>(request.getPageNum(), request.getPageSize(), true);

        QueryWrapper<TraceLog> wrapper = new QueryWrapper<>(traceLogListPageRequestToTraceLogConverter.convert(request));
        if (Objects.nonNull(request.getElapsedTime())) {
            wrapper.gt("elapsed_time", request.getElapsedTime());
        }

        if (StringUtils.isNotEmpty(request.getOrderByKey())) {
            if (request.getOrderByDesc() != null && request.getOrderByDesc().booleanValue() == false) {
                wrapper.orderByAsc(request.getOrderByKey());
            } else {
                wrapper.orderByDesc(request.getOrderByKey());
            }
        }

        IPage<TraceLog> pageResult = traceLogMapper.selectPage(page, wrapper);

        PageResult<TraceLogResponse> response = new PageResult(
                pageResult.getTotal(), pageResult.getPages(), pageResult.getCurrent(), pageResult.getSize(),
                pageResult.getRecords().stream().map(traceLogToResponseConverter::convert).collect(Collectors.toList()));
        return ResultDTO.success(response);
    }

}
